/* not type checking this file because flow doesn't play well with Proxy */

import config from 'core/config'
import { warn, makeMap, isNative } from '../util/index'

// 声明 initProxy 变量
let initProxy

if (process.env.NODE_ENV !== 'production') {
  // 判断给定的 key 是否出现在上面字符串中定义的关键字中的
  const allowedGlobals = makeMap(
    'Infinity,undefined,NaN,isFinite,isNaN,' +
    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
    'require' // for Webpack/Browserify
  )
  // 通过 warn 打印一段警告信息，警告信息提示你“在渲染的时候引用了 key，但是在实例对象上并没有定义 key 这个属性或方法” 
  const warnNonPresent = (target, key) => {
    warn(
      `Property or method "${key}" is not defined on the instance but ` +
      'referenced during render. Make sure that this property is reactive, ' +
      'either in the data option, or for class-based components, by ' +
      'initializing the property. ' +
      'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.',
      target
    )
  }

  const warnReservedPrefix = (target, key) => {
    warn(
      `Property "${key}" must be accessed with "$data.${key}" because ` +
      'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
      'prevent conflicts with Vue internals' +
      'See: https://vuejs.org/v2/api/#data',
      target
    )
  }

  // 判断宿主环境是否支持 js 原生的 Proxy 特性
  const hasProxy =
    typeof Proxy !== 'undefined' && isNative(Proxy)

  if (hasProxy) {
    // isBuiltInModifier 函数用来检测是否是内置的修饰符
    const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact')
    // 为 config.keyCodes 设置 set 代理，防止内置修饰符被覆盖，如通过Vue.config.keyCodes.shift = 16来修改shift默认值 
    config.keyCodes = new Proxy(config.keyCodes, {
      set (target, key, value) {
        if (isBuiltInModifier(key)) {
          warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
          return false
        } else {
          target[key] = value
          return true
        }
      }
    })
  }
  // 定义hasHandler常量（has 可以拦截 with 语句块里对变量的访问）
  const hasHandler = {
    has (target, key) {
      // has 常量是真实经过 in 运算符得来的结果
      const has = key in target
      // 如果 key 在 allowedGlobals 之内，或者 key 是以下划线 _ 开头的字符串，则为真
      const isAllowed = allowedGlobals(key) ||
        (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
      // 如果 has 和 isAllowed 都为假，使用 warnNonPresent 函数打印错误 
      // 即访问了一个没有定义在实例对象上(或原型链上)的属性但如果你访问的是全局对象时，也是允许的
      if (!has && !isAllowed) {
        if (key in target.$data) warnReservedPrefix(target, key)
        else warnNonPresent(target, key)
      }
      return has || !isAllowed
    }
  }

  // 检测到访问的属性不存在时给出一个警告
  const getHandler = {
    get (target, key) {
      if (typeof key === 'string' && !(key in target)) {
        if (key in target.$data) warnReservedPrefix(target, key)
        else warnNonPresent(target, key)
      }
      return target[key]
    }
  }
  // 在这里初始化 initProxy变量
  initProxy = function initProxy (vm) {
    // 如果当前环境支持Proxy 特性
    if (hasProxy) {
      // options 就是 vm.$options 的引用
      const options = vm.$options
      // 实际是给vm实例做一个hasHandler代理，并将代理对象挂在_renderProxy上，使得在访问vm._renderProxy对象上的属性时先调用has方法，
      // 返回值为真才去取该属性。这在后面的_render函数会用到
      // handlers 既可能是 getHandler 又可能是 hasHandler， 而options.render._withStripped 这个属性只在测试代码中出现过，所以一般情况下这个条件都会为假
      // options.render._withStripped只在 test/unit/features/instance/render-proxy.spec.js出现过。
      const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
  }
}

export { initProxy }
